package cn.kennylee.codehub.mongodb.das.extension;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONUtil;
import cn.kennylee.codehub.common.das.AbstractDasQueryStrategy;
import cn.kennylee.codehub.common.das.DasQueryStrategy;
import cn.kennylee.codehub.common.das.QueryCriteriaDto;
import cn.kennylee.codehub.common.das.dto.PageDto;
import cn.kennylee.codehub.mongodb.das.entity.MongoDbEo;
import cn.kennylee.codehub.mongodb.das.utils.MongoDbDasHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Collectors;

/**
 * MongoDB查询策略
 * <p>Created on 2024/12/11.</p>
 *
 * @author kennylee
 * @since 0.0.1
 */
@Slf4j
public class MongodbDasQueryStrategy<P extends Serializable, E extends PageDto, T extends MongoDbEo<P>> extends AbstractDasQueryStrategy<E, T, Criteria, Query> {

    /**
     * 查询策略可单例
     */
    @SuppressWarnings({"rawtypes"})
    private static final MongodbDasQueryStrategy INSTANCE = new MongodbDasQueryStrategy<>();

    private static final Set<String> IGNORE_PROPS = new HashSet<>();

    static {
        IGNORE_PROPS.addAll(Arrays.stream(ClassUtil.getDeclaredFields(PageDto.class)).filter(it -> !Modifier.isFinal(it.getModifiers())).map(Field::getName).collect(Collectors.toSet()));
        if (log.isDebugEnabled()) {
            log.debug("忽略的搜索条件dto字段名列表: {}", IGNORE_PROPS);
        }
    }

    @Override
    protected Criteria newCriteria(String propName) {
        return Criteria.where(propName);
    }

    @Override
    protected Query getQuery() {
        return new Query();
    }

    @Override
    protected void addIsNotNullCriteria(Criteria criteria, String propName) {
        criteria.ne(null);
    }

    @Override
    protected void addIsNullCriteria(Criteria criteria, String propName) {
        criteria.isNull();
    }

    @Override
    protected void addNotLikeCriteria(Criteria criteria, String propName, Object val) {
        // 判断只有字符串类型才能模糊查询
        Assert.isTrue(ReflectUtil.getField(getEntityClass(), propName).getType().equals(String.class),
            "只有字符串类型才能模糊查询");

        criteria.not().regex(MongoDbDasHelper.toLikeRegex(val.toString()));
    }

    @Override
    protected void addSufLikeCriteria(Criteria criteria, String propName, Object val) {
        // 判断只有字符串类型才能模糊查询
        Assert.isTrue(ReflectUtil.getField(getEntityClass(), propName).getType().equals(String.class),
            "只有字符串类型才能模糊查询");

        String likeRegex = MongoDbDasHelper.toSufLikeRegex(val.toString());
        log.debug("添加后模糊查询条件：{} like {}", propName, likeRegex);

        criteria.regex(likeRegex);
    }

    @Override
    protected void addPreLikeCriteria(Criteria criteria, String propName, Object val) {
        // 判断只有字符串类型才能模糊查询
        Assert.isTrue(ReflectUtil.getField(getEntityClass(), propName).getType().equals(String.class),
            "只有字符串类型才能模糊查询");
        String likeRegex = MongoDbDasHelper.toPreLikeRegex(val.toString());
        log.debug("添加前模糊查询条件：{} like {}", propName, likeRegex);

        criteria.regex(likeRegex);
    }

    @Override
    protected void addNotInCriteria(Criteria criteria, String propName, Collection<?> vals) {
        if (log.isDebugEnabled()) {
            log.debug("添加not in查询条件：{} not in {}", propName, JSONUtil.toJsonStr(vals));
        }
        criteria.nin(vals);
    }

    @Override
    protected void addInCriteria(Criteria criteria, String propName, Collection<?> vals) {
        if (log.isDebugEnabled()) {
            log.debug("添加in查询条件：{} in {}", propName, JSONUtil.toJsonStr(vals));
        }
        criteria.in(vals);
    }

    @Override
    protected void addLikeCriteria(Criteria criteria, String propName, Object val) {

        // 判断只有字符串类型才能模糊查询
        Assert.isTrue(ReflectUtil.getField(getEntityClass(), propName).getType().equals(String.class),
            "只有字符串类型才能模糊查询");

        String likeRegex = MongoDbDasHelper.toLikeRegex(val.toString());
        log.debug("添加模糊查询条件：{} like {}", propName, likeRegex);

        criteria.regex(likeRegex);
    }

    @Override
    protected void addLeCriteria(Criteria criteria, String propName, Object val) {
        if (log.isDebugEnabled()) {
            log.debug("添加小于等于查询条件：{} <= {}", propName, val);
        }
        criteria.lte(convertEpochMilli(propName, val));
    }

    @Override
    protected void addLtCriteria(Criteria criteria, String propName, Object val) {
        if (log.isDebugEnabled()) {
            log.debug("添加小于查询条件：{} < {}", propName, val);
        }
        criteria.lt(convertEpochMilli(propName, val));
    }

    @Override
    protected void addGeCriteria(Criteria criteria, String propName, Object val) {
        if (log.isDebugEnabled()) {
            log.debug("添加大于等于查询条件：{} >= {}", propName, val);
        }
        criteria.gte(convertEpochMilli(propName, val));
    }

    @Override
    protected void addGtCriteria(Criteria criteria, String propName, Object val) {
        if (log.isDebugEnabled()) {
            log.debug("添加大于查询条件：{} > {}", propName, val);
        }
        criteria.gt(convertEpochMilli(propName, val));
    }

    @Override
    protected void addEqCriteria(Criteria criteria, String propName, Object val) {
        if (Objects.isNull(val)) {
            return;
        }
        if (log.isDebugEnabled()) {
            log.debug("添加相等查询条件：{} = {}", propName, val);
        }
        criteria.is(val);
    }

    @Override
    protected void addNeCriteria(Criteria criteria, String propName, Object val) {
        criteria.ne(val);
    }

    /**
     * 重载此方法，使用Criteria.andOperator()方法，批量增加字段，而不是单独增加，方便后续扩展
     * <p>因为字段key只能增加一次addCriteria，否则会抛出异常</p>
     */
    @Override
    protected Query parseQueryCriteriaDtos(Query query, Map<String, List<QueryCriteriaDto>> queryCriteriaDtoMap) {
        List<Criteria> criteria = new ArrayList<>();
        queryCriteriaDtoMap.forEach((entityPropName, queryCriteriaDtos) -> {
            Criteria addCriteria = addCriteria(convertDbPropName(entityPropName), queryCriteriaDtos);
            if (MongoDbDasHelper.isNotSetValue(addCriteria)) {
                log.warn("有非空字段，但查询条件为空，字段：{}，忽略", entityPropName);
                return;
            }
            criteria.add(addCriteria);
        });
        if (false == criteria.isEmpty()) {
            query.addCriteria(new Criteria().andOperator(criteria.toArray(new Criteria[0])));
        }
        return query;
    }

    @Override
    protected Set<String> ignoreSearchProps() {
        return Collections.unmodifiableSet(IGNORE_PROPS);
    }

    protected Object convertEpochMilli(String fieldName, Object val) {
        return MongoDbDasHelper.toEpochMilliIfNecessary(val, getEntityClass(), fieldName);
    }

    @Override
    @SuppressWarnings("unchecked")
    public DasQueryStrategy<E, T> newInstance() {
        return INSTANCE;
    }
}
